package com.zh.test.ProxyAndAspect;


import com.zh.test.bean.AspectLogs;

import com.zh.test.bean.Person;
import com.zh.test.util.springUtil2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * spring  aspect切面功能
 *
 * @ Pointcut 表达式有
 * execution：用于匹配方法执行的连接点；
 * <p>
 * within：用于匹配指定类型内的方法执行；
 * <p>
 * this：用于匹配当前AOP代理对象类型的执行方法；注意是AOP代理对象的类型匹配，这样就可能包括引入接口也类型匹配；
 * <p>
 * target：用于匹配当前目标对象类型的执行方法；注意是目标对象的类型匹配，这样就不包括引入接口也类型匹配；
 * <p>
 * args：用于匹配当前执行的方法传入的参数为指定类型的执行方法；
 * @ within：用于匹配所以持有指定注解类型内的方法；
 * @ target：用于匹配当前目标对象类型的执行方法，其中目标对象持有指定的注解；
 * @ args：用于匹配当前执行的方法传入的参数持有指定注解的执行；
 * @ annotation：用于匹配当前执行方法持有指定注解的方法；
 * <p>
 * bean：Spring AOP扩展的，AspectJ没有对于指示符，用于匹配特定名称的Bean对象的执行方法
 * <p>
 * https://blog.csdn.net/zhengchao1991/article/details/53391244?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
 */

@Component //标识组件
@Aspect //标识切面
@Lazy() //延后加载
public class LogAspect {

    Logger logger = LoggerFactory.getLogger(LogAspect.class);
    @Autowired
    HttpServletRequest request;


    /**
     * https://blog.csdn.net/qq_26860451/article/details/100554377
     * 这是根据表达式,针对方法级别的插入点
     *
     * @ Pointcut 这是插入点  modifiers-pattern?        ret-type-pattern   declaring-type-pattern?name-pattern(param-pattern) throws-pattern?
     * 方法可见性(public/private) 返回值类型           类全路径               方法名      (参数类型全路径)   异常类型
     */
    @Pointcut("execution(* com.zh.test.controller.testController.*(..))")
    public void cutMethod() {
    }

    /**
     * 这是根据注解的插入点,当栈运行到指定直接时,直接切入,所以可以在requestMap进行切入,@Pointcut这个注解无视流程可以直接截断
     */
    @Pointcut("@annotation(com.zh.test.bean.AspectLogs)")
    public void AnnoCut() {

    }


    @Before("cutMethod()")
    public void before() {
        logger.info("这是开始的切入点");
        request.getSession().setAttribute("name", "张辉");
        System.out.print(springUtil2.getBean(Person.class).getName());
    }

    @After("cutMethod()")
    public void after() {

        logger.info("这是结束的切入点  {}", request.getSession().getAttribute("name"));
    }

    @Around("cutMethod()") //异常交给上层方法
    public Object round(ProceedingJoinPoint point) throws Throwable {


        logger.info("session中name={}", request.getSession().getAttribute("name"));
        request.getSession().setAttribute("name", "李四");
        logger.info("这是环绕的切入点");
        String name = point.getSignature().getName(); //获取当前拦截点名字 因为spring是精确到方法的,是针对方法级别进行编程,所有当前目标都是方法
        Object className = point.getTarget();//获取当前实例对象
        Class clazz = className.getClass();//获取实例对象的类
        Object[] args = point.getArgs(); //获取当前方法参数数组,按顺序
        // clazz.getMethod(name,);
        Class[] c = ((MethodSignature) point.getSignature()).getParameterTypes();//获取当前类的形参类型数组
        try {
            Method method1 = ((MethodSignature) point.getSignature()).getMethod();
            Method method = clazz.getMethod(name, c);
            AspectLogs aspectLogs = method1.getAnnotation(AspectLogs.class);
            if (null != aspectLogs) {
                logger.info("切面日志={}", aspectLogs.AspectLog());
                logger.info("切面枚举={}", aspectLogs.AspectEnums());
            }


        } catch (Exception e) {
            e.printStackTrace();
        }
        Object proceed = null;
        //todo 要不就方法声明上加上抛出throwable 而不是Exception
        //要不在这里手动try catch异常
         //todo  手动释放业务方法的返回值,切面增强方法包裹 业务方法,当业务方法执行完毕后,会抛出返回值/异常值给增强方法,需要手动释放给下一层
        proceed = point.proceed();


        return proceed;
    }

    @AfterReturning("cutMethod()")
    public void returnCon() {
        logger.info("这是返回切入点");
    }

    @Around("AnnoCut()")
    public Object roundAnno(ProceedingJoinPoint point) throws Exception {
        Object p = null;
        try {
            p = point.proceed(); //todo 被切点继续执行
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            throw  new Exception("当场抛出方法");
        }
        String name = point.getSignature().getName(); //todo 获取当前实例的名字 由于Spring是颗粒度是方法级别,所以作用的是方法上的 ||Signature签名
        Class clazz = point.getTarget().getClass();//todo spring是方法级别的颗粒度,所有他的target是类级别 ||target目标
        Class[] c = ((MethodSignature) point.getSignature()).getParameterTypes();
        Method m = null;
        try {
            m = clazz.getMethod(name, c);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        AspectLogs a = m.getAnnotation(AspectLogs.class);

        logger.info("这是采用注释切入点{}", a.AspectEnums());
        return p;
    }
}
